/*
 * Copyright 2010 Christoph Widulle (christoph.widulle@googlemail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */



package com.android1.amarena2d.engine;

import com.android1.amarena2d.annotations.NotThreadsave;
import com.android1.amarena2d.commons.Callback;
import com.android1.amarena2d.commons.collections.IntHashMap;
import com.android1.amarena2d.commons.event.Event;
import com.android1.amarena2d.commons.event.EventHandler;

import java.util.ArrayList;


@NotThreadsave
public class EventBus {

    public static final int NOT_PROCESSING = -9999;

    private final IntHashMap<EventHandlerList> eventHandlerRegistry;
    private final ArrayList<EventHandler> toRemove = new ArrayList<EventHandler>();

    private int processingEvent = NOT_PROCESSING;


    public EventBus() {
        this.eventHandlerRegistry = new IntHashMap<EventHandlerList>();
        Event.createPool(10);
    }


    public void register(int eventType, EventHandler eventHandler) {
        assert eventType != 0 : "EventType cannot be 0. Add a public final int TYPE = ? to the event.";
        assert eventHandler != null;

        EventHandlerList handlerList = eventHandlerRegistry.get(eventType);
        if (handlerList == null) {
            handlerList = new EventHandlerList(eventType);
            eventHandlerRegistry.put(eventType, handlerList);
        }
        handlerList.list.add(eventHandler);
    }

    public CallbackEventHandler register(int eventType, Callback<Event> callback) {
        assert eventType != 0 : "EventType cannot be 0. Add a public final int TYPE = ? to the event.";
        assert callback != null;

        CallbackEventHandler handler = new CallbackEventHandler(callback);
        register(eventType, handler);
        return handler;
    }

    public void unregister(int eventType, EventHandler eventHandler) {

        //if the eventType is currently processing, we cant remove the EventHandler directly.
        if (eventType == processingEvent) {
            toRemove.add(eventHandler);
        } else {

            EventHandlerList handler = eventHandlerRegistry.get(eventType);
            if (handler == null) {
                return;
            }
            handler.list.remove(eventHandler);
        }
    }


    public void fire(Event event) {
        internalFire(event);
    }

    public void fire(int eventType) {
        Event e = Event.obtain(eventType);
        internalFire(e);
    }

    public void clear() {
        eventHandlerRegistry.clear();
    }


    @SuppressWarnings("unchecked")
    private void internalFire(Event event) {
        processingEvent = event.getType();
        final EventHandlerList eventHandlerList = eventHandlerRegistry.get(processingEvent);
        if (eventHandlerList != null) {
            final ArrayList<EventHandler> handlerList = eventHandlerList.list;
            if (handlerList != null) {
                int s = handlerList.size();
                for (int i = 0; i < s; i++) {
                    EventHandler handler = handlerList.get(i);
                    handler.onEvent(event);
                }
            }
            if (event.isRecyclable()) {
                event.recycle();
            }
            //Tricky way to avoid ConcurrentModificationException or IndexOutOfBoundsException
            if (toRemove.size() > 0) {
                for (int i = 0; i < toRemove.size(); i++) {
                    EventHandler eventHandler = toRemove.get(i);
                    eventHandlerList.list.remove(eventHandler);
                }

            }
        }
        processingEvent = NOT_PROCESSING;

    }

    private static class CallbackEventHandler extends EventHandler {
        Callback<Event> callback;

        private CallbackEventHandler(Callback<Event> callback) {
            this.callback = callback;
        }

        @Override
        public void onEvent(Event baseEvent) {
            callback.on(baseEvent);
        }
    }

    public static class EventHandlerList {
        int eventType;
        ArrayList<EventHandler> list = new ArrayList<EventHandler>();


        public EventHandlerList(int eventType) {
            this.eventType = eventType;
        }
    }


}
